package com.zws.cucumber.glue;

import com.zws.cucumber.expander.ZwsStr;
import com.zws.cucumber.expr.ZwsExpr;
import com.zws.cucumber.util.colorfullog.ZwsColorfulLogger;
import com.zws.cucumber.util.colorfullog.ZwsPrintTableHelper;
import io.cucumber.datatable.DataTable;
import io.cucumber.java.en.Given;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.junit.jupiter.api.Assertions;
import org.slf4j.Logger;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.*;
import java.util.stream.Collectors;

import static com.zws.cucumber.expander.ZwsStr.expand;

/**
 * author: zws
 */
public class ZwsCvsStepDef {

    private static final Logger logger = ZwsColorfulLogger.of(ZwsCvsStepDef.class);

    @Given("verify data in csv content {string}")
    public void verifyDataInTableWithWhereClause(String content, DataTable dataTable) throws IOException {
        verifyDataInTableWithWhereClause(content, ",", dataTable);
    }

    @Given("verify data in csv content {string} with field separator {string}")
    public void verifyDataInTableWithWhereClause(String content, String fieldSep, DataTable dataTable) throws IOException {

        content = ZwsStr.expand(content);

        List<List<String>> actualData = new ArrayList<>();
        try (BufferedReader br = new BufferedReader(new StringReader(content))) {
            String row;
            while ((row = br.readLine()) != null) {
                String[] fields = StringUtils.splitPreserveAllTokens(row, fieldSep);
                if (fields != null) {
                    actualData.add(
                            Arrays.stream(fields)
                                    .map(String::trim)
                                    .collect(Collectors.toCollection(ArrayList::new)));
                } else {
                    actualData.add(List.of(""));
                }
            }
        }

        // print the actual data - start
        int columnSize = actualData.stream()
                .max(Comparator.comparingInt(List::size))
                .orElseGet(ArrayList::new)
                .size();
        List<String> columnNameList = new ArrayList<>();
        for (int i = 0; i < columnSize; i++) {
            columnNameList.add("Column %d".formatted(i));
        }
        List<Map<String, Object>> printableActualDataList = new ArrayList<>();
        actualData.forEach(rl -> {
            Map<String, Object> rm = new HashMap<>();
            for (int i = 0; i < rl.size(); i++) {
                rm.put("Column %d".formatted(i), rl.get(i));
            }
            printableActualDataList.add(rm);
        });
        ZwsPrintTableHelper.printTable0(
                "Actual Data",
                Pair.of(columnNameList, printableActualDataList),
                true);
        // print the actual data - end

        List<List<String>> expectedData = dataTable.cells();

        Assertions.assertEquals(expectedData.size(), actualData.size());

        Set<Integer> matchedIndexes = new HashSet<>();
        for (int i = 0; i < expectedData.size(); i++) {
            List<String> expectedRow = expectedData.get(i);
            logger.debug("try to match expected row {}", i);
            boolean hasSame = false;
            for (int j = 0; j < actualData.size(); j++) {
                if (matchedIndexes.contains(j)) {
                    continue;
                }
                logger.debug("<c>----------------try to compare expect row {} to actual row {}</c>", i, j);
                List<String> actualRow = actualData.get(j);
                if (isRowEquals(expectedRow, actualRow)) {
                    logger.debug("<g><bold>----------------expect row {} match with actual row {}</bold></g>", i, j);
                    matchedIndexes.add(j);
                    hasSame = true;
                    break;
                } else {
                    logger.debug("<y><bold>----------------expect row {} doesn't match with actual row {}</bold></y>", i, j);
                }
            }
            if (!hasSame) {
                Assertions.fail("no row equals, expected row: " + expectedRow + ", actual row list: " + actualData);
            }
        }
    }

    private boolean isRowEquals(List<String> expectedRow, List<String> actualRow) {
        // not like row in database
        if (expectedRow.size() < actualRow.size()) {
            return false;
        }
        for (int i = 0; i < expectedRow.size(); i++) {
            // get expected field
            String expectedField = expectedRow.get(i);
            // get and normalize actual field
            Object actualField = null;
            if (actualRow.size() > i) {
                actualField = actualRow.get(i);
            }

            if (!ZwsExpr.verifySilently("Column %d".formatted(i), expand(expectedField), actualField)) {
                return false;
            }
        }
        return true;
    }

}
